#include <string>
#include <memory>
#include <mutex>
#include <fstream>
#include <boost/format.hpp>

namespace sorb { 
namespace logging {

enum LogLevel
{
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL,
    NUM_LOG_LEVELS,
};

extern const char* LogLevelName[];

LogLevel logLevel();

void setLogLevel(LogLevel level);

void setLogFilePath(const std::string& new_path);

std::string getTimeStamp();

class ExchangeBuf;

class Impl {
public:
    Impl(); 

    template<typename T, typename... Args>
    void _log(boost::format& fmt, T arg1, Args&&... args);

    void _log(boost::format& fmt);

    void log(std::string&& msg);

    void run();

    ~Impl();

private:
    void swapBuf();
    void rotate();

    std::fstream _ofs;
    std::shared_ptr<ExchangeBuf> _log_buf;
    std::shared_ptr<ExchangeBuf> _write_buf;
    std::mutex _mutex;

};

class Logger {
public:
    static Logger* getInstance();

    template<typename... Args>
    void log(const char* file, int line, const char* func, LogLevel level, const std::string& fmtStr, Args... args);


private:
    Logger();
    ~Logger() {};
    std::shared_ptr<Impl> _impl;
};


template<typename... Args>
void Logger::log(const char* file, int line, const char* func, LogLevel level,const std::string& fmtStr, Args... args) {
    //__FILE__ & __LINE__ is not that important, skip it for now
    std::string prefix = getTimeStamp() + " " + func + " " + LogLevelName[level] + " ";
    boost::format fmt(prefix + fmtStr);
    _impl->_log(fmt, args...);
}


template<typename T, typename... Args>
void Impl::_log(boost::format& fmt, T arg1, Args&&... args) {
    _log(fmt % arg1, args...);
}

#define LOG_FILE(arg) sorb::logging::setLogFilePath(arg);

#define LOG_LEVEL(arg) sorb::logging::setLogLevel(sorb::logging::LogLevel::arg);

#define LOG_TRACE(arg...) if(sorb::logging::logLevel() <= sorb::logging::TRACE) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::TRACE ,##arg);

#define LOG_DEBUG(arg...) if(sorb::logging::logLevel() <= sorb::logging::DEBUG) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::DEBUG, ##arg);

#define LOG_INFO(arg...) if(sorb::logging::logLevel() <= sorb::logging::INFO) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::INFO, ##arg);
                    
#define LOG_WARN(arg...) if(sorb::logging::logLevel() <= sorb::logging::WARN) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::WARN, ##arg);

#define LOG_ERROR(arg...) if(sorb::logging::logLevel() <= sorb::logging::ERROR) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::ERROR, ##arg);

#define LOG_FATAL(arg...) if(sorb::logging::logLevel() <= sorb::logging::FATAL) \
                        sorb::logging::Logger::getInstance()->log(__FILE__, __LINE__, __func__, sorb::logging::FATAL, ##arg);


}// namespace sorb
}// namespace logging



